package thread;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: wx
 * Date: 2024-01-06
 * Time: 17:20
 */

/** 懒汉模式 创建的时机不太一样
 *          创建实例的机会更晚,值到第一次使用的时候,才会创建实例
 *
 */


// 使用懒汉的方式实现单例模式
class Singletonlazy {
    //此处引用指向唯一实例,这里先创建实例为空,后进行调度
    private static Singletonlazy instance = null;

    public static Singletonlazy getInstance() {
        if (instance == null) {
            instance = new Singletonlazy();
        }
        return instance;
    }
    /*
      如果是首次调用 getinstance,那么此时 instance引用为 null, 就会进入if条件,
    从而创建出实例.
       如果是后续再度调用 getinstance,由于 instance 已经不再是 null  此时就不会进入 if.
    而是直接返回之前创造好的引用了.

       这样的设定,仍然可以保证,该类的实例是 唯一 一个.
    于此同时,创建实例的时机就不是程序驱动时了,而是第一次调用 getinstance 的时候

                                       这个调用 getinstance 操作的执行时机就不知道了,就得看程序的时机需求
                                    大概率要比饿汉模式这种方式要晚一点. 甚至有可能整个进程压根用不到这个方法,
                                    也就把创建的操作给省下了.

     譬如说 肯德基的 "疯狂星期四"
       对于肯德基"点餐系统"来说,就可以用来判定今天是不是星期四
     如果是"星期四",才进行加载"疯狂星期四"的相关逻辑和概念
     如果不是"星期四",就不用加载了. (节省了一定的开销)
    */
    //将构造方法设为私有的
    private Singletonlazy() {

    }
}
public class ThreadDemo26 {
    public static void main(String[] args) {
        Singletonlazy s1 = Singletonlazy.getInstance();
        Singletonlazy s2 = Singletonlazy.getInstance();
        System.out.println(s1 == s2);
    }
}
// 在这里有一个问题,上面所编写的 饿汉模式 和 懒汉模式,是否是线程安全的?

/*

  如果在多个线程中,并发调用 getinsance 这个两个模式是否是属于线程安全的?
      1)对于饿汉模式来说 getinstance 直接返回 instance 这个实例,这个操作本质就是"读操作".
       多个线程读取同一个数据,这个操作是线程安全的.
          所以对于"饿汉模式"来说,这个操作是线程安全的
      2)在懒汉模式中,代码中的操作有读有写.
        多个线程同时读取和写入同一个数据,这个操作是线程不安全的
          所以对于"恶汉模式"来说,这个操作不是线程安全的
*/

//如何解决懒汉模式的线程安全问题,让他成为安全的代码??


//通过加锁 synchronized 可以解决这个问题吗?具体应该怎么加呢?
class Singletonlazy1 {
    //此处引用指向唯一实例,这里先创建实例为空,后进行调度
    private static Singletonlazy1 instance = null;
    private static Object locker = new Object();
    public static Singletonlazy1 getInstance() {              // 这个代码就不能去解决线程安全问题
        if (instance == null) {                               // 要怎么让这个代码执行正确?
            synchronized (locker) {
                instance = new Singletonlazy1();              // 就需要将 if 和 new 两个操作打包成一个原子的.
            }
        }
            return instance;
    }
    private Singletonlazy1() {

    }
}
//修改版，将 if 和 new 打包在一起
class Singletonlazy2 {
    //此处引用指向唯一实例,这里先创建实例为空,后进行调度
    private static Singletonlazy2 instance = null;
    private static Object locker = new Object();
    public static Singletonlazy2 getInstance() {
        synchronized (locker) {
         if (instance == null) {
                instance = new Singletonlazy2();
            }
        }
          return instance;
    }
    private Singletonlazy2() {

    }
}
/*
  上述代码仍然存在一定的问题
    如果 instance 已经创建过了,此后在调用 getinstance 就都是直接返回 instance 实例了
     (此处的操作就是纯粹的读操作了,也就不会有线程安全问题了)
     此时,针对这个已经没有线程安全问题的代码,仍然每次调用都先加锁,在解锁.此时,效率就非常低了.
      加锁就意味着可能会产生阻塞,
      一旦线程阻塞,啥时候能解除,就不知道了
      (你可以认为,只要一个代码里加锁了,基本就注定和"高性能"无关了)
  那么如何解决呢?
  在加锁外头在套上一层 if 判定一下代码是否需要加锁
  如果需要加锁,才加,如果不需要就不用加锁了
*/


//最终修改版

class Singletonlazy3 {
    //此处引用指向唯一实例,这里先创建实例为空,后进行调度
    private static Singletonlazy3 instance = null;
    private static Object locker = new Object();
    public static Singletonlazy3 getInstance() {
        //首次调用就要考虑线程安全问题
        // 如果 instance 为 null,就说明是首次调用,首次调用就要考虑线程安全问题,就需要加锁.
        // 如果"非空"的话就说明是后续的调用,就不必加锁了
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new Singletonlazy3();
                }
            }
        }
        return instance;
    }
    private Singletonlazy3() {

    }
}
/**上述代码还是有问题的,还存在的问题就是 "指令重排序" 了
 * public static Singletonlazy3 getInstance() {
 *         //首次调用就要考虑线程安全问题
 *         // 如果 instance 为 null,就说明是首次调用,首次调用就要考虑线程安全问题,就需要加锁.
 *         // 如果"非空"的话就说明是后续的调用,就不必加锁了
 *         if (instance == null) {
 *             synchronized (locker) {
 *                 if (instance == null) {
 *                     instance = new Singletonlazy3();
 *                 }
 *             }
 *         }
 *         return instance;
 *     }
 *     这里就有指令重排序引起的线程安全问题了
 *     指令重排序,也是编译器优化的一种方式
 *       调整原有代码的执行顺序,保证逻辑不变的前提下,提高程序的效率
 *
 *     举个栗子
 *     我们需要买菜        超市入口|   *   *   *   *   *  |超市出口
 *     西红柿                     黄  茄  西   鸡   其
 *     鸡蛋                      瓜  子  红   蛋   他
 *     黄瓜                             柿
 *     茄子
 *
 *     如果按我们之前菜单上的方式买菜,就需要多个地方多次跑
 *     如果我们对菜单顺序进行重排序,按照新的顺序来执行,效率就会更高
 *       instance = new Singletonlazy3();
 *       拆成三个过程
 *       1.申请空间
 *       2.内存上调用构造方法,创建出实例
 *       3.内存地址赋值给 instance 引用变量
 *
 *      正常情况下,上述代码按照 1 2 3 来执行的,但是也可以优化成 1 3 2 这种
 *      无论是那种情况,在单线程下,都是可以的
 *      但是在多线铖下,指令重排序,就会引入问题了
 *      如果是两个线程, t1 和 t2 ,由于 t1 线程刚执行完 1 3 被调度走,此时 instance 指向的是一个非 null 的,但是未初始化对象.
 *      此时 t2 线程判定 instance == null 不成立,直接 return.如果 t2 继续使用 instance 里面的属性 或者 方法,就会出现问题
 *      (此时这里的属性都是未初始化的"全'0'值").就可能会引起代码的逻辑出现问题
 *
 *      为了解决上述问题.核心思路,还是使用 volatile
 *                                   1) 保证内存可见性 每次访问变量,必须都要重新读取内存,而不会优化到缓存器/缓存中
 *                                   2) 禁止指令重排序 针对这个被 volatile 修饰的变量的读写操作相关的指令,是不能被重排序的
 */
class Singletonlazy4 {
    //此处引用指向唯一实例,这里先创建实例为空,后进行调度
    private volatile static Singletonlazy4 instance = null; //这个时候针对这个变量的读写操作,就不会出现重排序了
                                                            //此时 执行顺序是被固定住的,也就杜绝了"指令重排序"引起的问题了
    private static Object locker = new Object();
    public static Singletonlazy4 getInstance() {
        //首次调用就要考虑线程安全问题
        // 如果 instance 为 null,就说明是首次调用,首次调用就要考虑线程安全问题,就需要加锁.
        // 如果"非空"的话就说明是后续的调用,就不必加锁了
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new Singletonlazy4();
                }
            }
        }
        return instance;
    }
    private Singletonlazy4() {

    }
}